package org.smoozik.services.sound;

import java.util.HashMap;
import java.util.Hashtable;

import javax.sound.midi.Instrument;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Patch;
import javax.sound.midi.Soundbank;
import javax.sound.midi.Synthesizer;

public class Synthe {
	private Synthesizer synthesizer;
	private Instrument[] instrumentsList;
	private Hashtable<String, Instrument> liste;
	private MidiChannel[] channels;
	private Hashtable<String, Instrument> instruments;
	private HashMap<Integer, Instrument> instrumentChannels;
	private Hashtable<String, Integer> notejouees;

	public Synthe() {
		try {
			instrumentChannels = new HashMap<Integer, Instrument>();
			synthesizer = MidiSystem.getSynthesizer();
			synthesizer.open();
			Soundbank bank = synthesizer.getDefaultSoundbank();

			channels = synthesizer.getChannels();
			instrumentsList = bank.getInstruments();
			liste = new Hashtable<>();
			Instrument instrument;
			String name;
			for (int j = instrumentsList.length - 1; j > 0; j--) {
				instrument = instrumentsList[j];
				name = instrument.getName();
				// System.out.println(j + " - '" + name + "'");
				liste.put(name, instrument);
			}
			program(0, "E.Piano 1   ");
			program(1, "E.Piano 1   ");

		} catch (Exception e) {
			e.printStackTrace();
			synthesizer = null;
		}

		notejouees = new Hashtable<>();
	}
	
	public Instrument getInstrumentByChannel(int channel) {
		return instrumentChannels.get(new Integer(channel));
	}

	public Instrument[] getInstrumentsList() {
		return instrumentsList;
	}

	public void setInstrumentsList(Instrument[] instrumentsList) {
		this.instrumentsList = instrumentsList;
	}

	public Hashtable<String, Instrument> getInstruments() {
		return instruments;
	}

	public void setInstruments(Hashtable<String, Instrument> instruments) {
		this.instruments = instruments;
	}

	public void program(int ch, String name) {

		Instrument instrument = liste.get(name);
		program(ch, instrument);
	}

	public void program(int ch, Instrument instrument) {
		if (synthesizer == null) {
			return;
		}

		if (instrument == null)
			return;

		instrumentChannels.put(new Integer(ch), instrument);

		synthesizer.loadInstrument(instrument);
		Patch patch = instrument.getPatch();

		if (patch != null) {
			channels[ch].programChange(patch.getBank(), patch.getProgram());
			System.out.println(instrument.getName());
		}

		for (int i = 0; i < 128; i++) {
			channels[ch].noteOff(i);
		}
	}

	public void play(int channel, int note, int velocity, int duration) {
		if (synthesizer == null)
			return;
		new PlayThread(channel, note, velocity, duration + 150).start();

	}

	public void close() {
		if (synthesizer == null)
			return;
		synthesizer.close();
	}

	class PlayThread extends Thread {
		int channel;
		int note;
		int velocity;
		int duration;

		public PlayThread(int channel, int note, int velocity, int duration) {
			super();
			this.channel = channel;
			this.note = note;
			this.velocity = velocity;
			this.duration = duration;
			String key = channel + "-" + note;
			Integer i1 = notejouees.get(key);
			if (i1 == null) {
				i1 = new Integer(0);
				notejouees.put(key, i1);
			}

			Integer i2 = new Integer(i1.intValue() + 1);
			notejouees.put(key, i2);
		}

		public void run() {
			channels[channel].noteOn(note, velocity);

			if (duration != 0) {
				try {
					Thread.sleep(duration);
				} catch (InterruptedException e) {
				}
			}

			String key = channel + "-" + note;
			Integer i1 = notejouees.get(key);
			if (i1 != null && i1.intValue() == 1) {
				channels[channel].noteOff(note);
			}

			Integer i2 = new Integer(i1.intValue() - 1);
			notejouees.put(key, i2);

		}
	}

	public static void main(String[] args) throws InterruptedException {
		Synthe synth = new Synthe();

		synth.play(0, 60, 127, 0);

		Thread.sleep(500);
		synth.play(0, 64, 127, 0);

		Thread.sleep(500);
		synth.play(0, 67, 127, 0);
		//
		// synth.program(0, "Recorder    ");
		Thread.sleep(500);

		synth.play(0, 72, 127, 0);
		Thread.sleep(1000);

		synth.close();
	}

}